/* ========================================================
* L4D SuperVersus
* ========================================================
* Created by DDRKhat
* Based upon Damizean's "L4D Spawn Missing Survivors"
* ========================================================
v1.5.4
-Hooked Round_Start. (Should Improve TankHP and Bot spawning)
v1.5.3
-Improved setting of TankHP (Should be definite now?)
v1.5.2
-Adjusted Survivor Spawning. (Should be the end of Survivor Spawning issues)
-Changed Finale Vehicle Handling. (Thanks to Damizean for the EntProp value!)
-Bot Booter only boots "Useless Bots" (Bots not directly involved with Useful/incap system)
v1.5.1
-Survivor Spawning moved "player_first_spawn" once more.
v1.5
-Official Left4Dead2 support
-Added client check on bot kicker (No more "Free Slot." disconnections)
-Bot Kicker message changed to "Kicking fake client" to help distinquish.
-XtraHP medpack spawning moved (Possibly fixes crashing on windows servers?)
-Survivor spawning moved to round_start, instant spawns survivors now
-Tank's Health is now using the server CVAR, could cause conflictions with other plugins.
v1.4
-Fixed Cvar forcing on survivor and infected limits
-CVAR Handle code improvements.
-Config file added (l4d_superversus.cfg inside of cfg/Soucemod)
-Tank HP changing now affects HUD
-Improved Tank monitoring
-Improved Left4DownTown checks
v1.3
-Fixed oversight preventing survivor joining
-Join commands now obey vs_max_team_switches
-Added Finale check. Makes saved survivors safe (To protect points)
-Added (If Left4Downtown 0.3.0 or later exists) Lobby Unreserving
-Fixed rare extra survivor
-Added option for Extra medpacks for extra survivors.
v1.2
-Increased Survivor/Infected limit to 18
-Added text-commands to join both teams (for those without console when the GUI fails)
+!jointeam2 / !joinsurvivor - Text equivalent of console command: jointeam 2
+!jointeam3 / !joininfected - Text equivalent of console command: jointeam 3
-Fixed stupid programming sight. Tank spawning fixed as a result.
-Various code cleanup
V1.1
-Adjusts the games built-in variables for handling survivor/infected limit
-Added a text-command people can use to join infected (Where the Switch Team GUI might fail)
-Added a command to increase zombie count.
V1.0
-Initial Release
*/

// *********************************************************************************
// PREPROCESSOR
// *********************************************************************************
// *********************************************************************************
#pragma semicolon 1                 // Force strict semicolon mode.
// INCLUDES
// *********************************************************************************
#include <sourcemod>
#include <sdktools>
#include <sdktools_functions>
#include <l4d_stocks>
#include <l4d_weapon_stocks>

#include "macros.sp"
#include "helpers.sp"

// *********************************************************************************
// OPTIONALS - If these exist, we use them. If not, we do nothing.
// *********************************************************************************
native L4D_LobbyUnreserve();
native L4D_LobbyIsReserved();
// *********************************************************************************
// CONSTANTS
// *********************************************************************************
#define CONSISTENCY_CHECK	1.0
#define DEBUG		0
#define PLUGIN_VERSION		"1.5.4"

// *********************************************************************************
// VARS
// *********************************************************************************
new Handle:SpawnTimer    		= INVALID_HANDLE;
new Handle:KickTimer    		= INVALID_HANDLE;
new Handle:SurvivorLimit 		= INVALID_HANDLE;
new Handle:InfectedLimit 		= INVALID_HANDLE;
new Handle:L4DSurvivorLimit 	= INVALID_HANDLE;
new Handle:L4DInfectedLimit 	= INVALID_HANDLE;
new bool:Useful[MAXCLIENTS];

// *********************************************************************************
// PLUGIN
// *********************************************************************************
public Plugin:myinfo =
{
	name        = "L4D SuperVersus",
	author      = "DDRKhat",
	description = "Allow versus to become up to 18vs18",
	version     = PLUGIN_VERSION,
	url         = "http://forums.alliedmods.net/showthread.php?t=92713"
};

// ------------------------------------------------------------------------
// OnPluginStart()
// ------------------------------------------------------------------------
public OnPluginStart()
{
	//////////
	// Convars
	//////////

	L4DSurvivorLimit = FindConVar("survivor_limit");
	L4DInfectedLimit   = FindConVar("z_max_player_zombies");
	SurvivorLimit = CreateConVar("l4d_survivor_limit","10","Maximum amount of survivors", CVAR_FLAGS,true,4.00,true,18.00);
	InfectedLimit = CreateConVar("l4d_infected_limit","10","Max amount of infected (will not affect bots)", CVAR_FLAGS,true,4.00,true,18.00);

	//////////
	// Convar handling
	//////////
	SetConVarBounds(L4DSurvivorLimit, ConVarBound_Upper, true, 18.0);
	SetConVarBounds(L4DInfectedLimit,   ConVarBound_Upper, true, 18.0);
	HookConVarChange(L4DSurvivorLimit, FSL);
	HookConVarChange(SurvivorLimit, FSL);
	HookConVarChange(L4DInfectedLimit, FIL);
	HookConVarChange(InfectedLimit, FIL);

	//////////
	// Events
	//////////
	HookEvent("round_start",Event_RoundStart);
	HookEvent("heal_begin",Event_UsefulBegin);
	HookEvent("heal_end",Event_UsefulEnd);
	HookEvent("revive_begin",Event_UsefulBegin);
	HookEvent("revive_end",Event_UsefulEnd);
	HookEvent("finale_vehicle_leaving", Event_FinaleVehicleLeaving);
	//////////
	// Load our config
	//////////
	AutoExecConfig(true, "l4d_superversus");

}

// ------------------------------------------------------------------------
// OnConvarChange()
// ------------------------------------------------------------------------
#define FORCE_INT_CHANGE(%1,%2,%3) public %1 (Handle:c, const String:o[], const String:n[]) { SetConVarInt(%2,%3); } 
FORCE_INT_CHANGE(FSL,L4DSurvivorLimit,GetConVarInt(SurvivorLimit))
FORCE_INT_CHANGE(FIL,L4DInfectedLimit,GetConVarInt(InfectedLimit))

// ------------------------------------------------------------------------
// OnMapEnd()
// ------------------------------------------------------------------------
public OnMapEnd() {if (SpawnTimer != INVALID_HANDLE){KillTimer(SpawnTimer);SpawnTimer = INVALID_HANDLE;}}

// ------------------------------------------------------------------------
// OnClientPutInServer - We have to use this because AIDirector Puts bots in, doesn't connect them.
// ------------------------------------------------------------------------
public OnClientPutInServer(client)
{
	if (SpawnTimer == INVALID_HANDLE&&TeamPlayers(2)<GetConVarInt(SurvivorLimit)) SpawnTimer = CreateTimer(CONSISTENCY_CHECK, SpawnTick, _, TIMER_REPEAT);
	if (KickTimer == INVALID_HANDLE&&TeamPlayers(2)>GetConVarInt(SurvivorLimit)) KickTimer = CreateTimer(CONSISTENCY_CHECK, KickTick, _, TIMER_REPEAT);

}

// ------------------------------------------------------------------------
// TeamPlayers() arg = teamnum
// ------------------------------------------------------------------------
public TeamPlayers(any:team)
{
	new int=0;
	for (new i=1; i<=MaxClients; i++)
	{
		if (!IsClientConnected(i)) continue;
		if (!IsClientInGame(i))    continue;
		if (GetClientTeam(i) != team) continue;
		int++;
	}
	return int;
}
// ------------------------------------------------------------------------
// RealPlayersInGame()
// ------------------------------------------------------------------------
bool:RealPlayersInGame ()
{
	for (new i=1;i<=GetMaxClients();i++)
		if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i))
			return true;
	return false;
}
// ------------------------------------------------------------------------
// OnClientDisconnect()
// ------------------------------------------------------------------------
public OnClientDisconnect(client)
{
	if (IsFakeClient(client)) return;
	if (!RealPlayersInGame()) { new i; for (i=1;i<=GetMaxClients();i++) CreateTimer(0.1, KickFakeClient, i); }
}

// ------------------------------------------------------------------------
// SpawnTick() 
// ------------------------------------------------------------------------
public Action:SpawnTick(Handle:hTimer, any:Junk)
{    
	// Determine the number of survivors and fill the empty
	// slots.
	new NumSurvivors = TeamPlayers(2);
	new MaxSurvivors = GetConVarInt(SurvivorLimit);
	if (NumSurvivors < 4)
	{
		KillTimer(SpawnTimer);
		SpawnTimer = INVALID_HANDLE;
		return Plugin_Stop;
	}
	// Create missing bots
	for (;NumSurvivors < MaxSurvivors; NumSurvivors++)
	{
		SpawnFakeClient();
	}
	// Once the missing bots are made, dispose of the timer
	KillTimer(SpawnTimer);
	SpawnTimer = INVALID_HANDLE;
	return Plugin_Stop;
}
// ------------------------------------------------------------------------
// KickTick()
// ------------------------------------------------------------------------
public Action:KickTick(Handle:hTimer, any:Junk)
{
	new NumSurvivors = TeamPlayers(2);
	new MaxSurvivors = GetConVarInt(SurvivorLimit);

	if (NumSurvivors < 4)
	{
		KillTimer(SpawnTimer);
		SpawnTimer = INVALID_HANDLE;
		return Plugin_Stop;
	}
	for (new i=1;i<=GetMaxClients();i++)
	{
		if(IsClientConnected(i)&&IsFakeClient(i)&&IsUseless(i)&&NumSurvivors>MaxSurvivors)
			CreateTimer(0.0, KickFakeClient, i);
		NumSurvivors--;
	}
	KillTimer(KickTimer);
	KickTimer = INVALID_HANDLE;
	return Plugin_Stop;
}

// ------------------------------------------------------------------------
// IsUseless() // Are we helping/being helped?
// ------------------------------------------------------------------------
bool:IsUseless(client)
{
	if(Useful[client] == false) return true;
	return false;
}

// ------------------------------------------------------------------------
// FinaleEnd() Thanks to Damizean for smarter method of detecting safe survivors.
// ------------------------------------------------------------------------
public Event_FinaleVehicleLeaving(Handle:event, const String:name[], bool:dontBroadcast)
{
	new edict_index = FindEntityByClassname(-1, "info_survivor_position");
	if (edict_index != -1)
	{
		new Float:pos[3];
		GetEntPropVector(edict_index, Prop_Send, "m_vecOrigin", pos);
		for(new i=1; i <= MaxClients; i++)
		{			
			if(IsValidSurvivorAlive(i))
			{

				if(L4D_IsPlayerIncapacitated(i))
				{
					ForcePlayerSuicide(i);
				}
				else
				{
					TeleportEntity(i, pos, NULL_VECTOR, NULL_VECTOR);

				}
			}
		}
	}
}
// ------------------------------------------------------------------------
// Event_UsefulBegin()
// ------------------------------------------------------------------------
public Event_UsefulBegin(Handle:event, const String:name[], bool:dontBroadcast)
{
	Useful[GetClientOfUserId(GetEventInt(event, "userid"))] = true; //Healer
	Useful[GetClientOfUserId(GetEventInt(event, "subject"))] = true; //Target
}

// ------------------------------------------------------------------------
// Event_UsefulEnd()
// ------------------------------------------------------------------------
public Event_UsefulEnd(Handle:event, const String:name[], bool:dontBroadcast)
{
	Useful[GetClientOfUserId(GetEventInt(event, "userid"))] = false; //Healer
	Useful[GetClientOfUserId(GetEventInt(event, "subject"))] = false; //Target
}
// ------------------------------------------------------------------------
// Event_RoundStart()
// ------------------------------------------------------------------------
public Event_RoundStart(Handle:event, const String:name[], bool:dontBroadcast)
{
	if (SpawnTimer == INVALID_HANDLE&&TeamPlayers(2)<GetConVarInt(SurvivorLimit)) SpawnTimer = CreateTimer(CONSISTENCY_CHECK, SpawnTick, _, TIMER_REPEAT);
	if (KickTimer == INVALID_HANDLE&&TeamPlayers(2)>GetConVarInt(SurvivorLimit)) KickTimer = CreateTimer(CONSISTENCY_CHECK, KickTick, _, TIMER_REPEAT);
}

SpawnFakeClient()
{
	// Spawn bot survivor.
	new Bot = CreateFakeClient("SurvivorBot");
	if (Bot == 0) return;

	ChangeClientTeam(Bot, TEAM_SURVIVORS);
	DispatchKeyValue(Bot, "classname", "SurvivorBot");

	CreateTimer(0.1, KickFakeClient, Bot);
}


public Action:KickFakeClient(Handle:hTimer, any:Client)
{
	if(IsClientConnected(Client) && IsFakeClient(Client))
	{
		KickClient(Client, "Kicking Fake Client.");
	}
	return Plugin_Handled;
}
